// Package repository
// Created by GoLand
// @User: lenora
// @Date: 2024/1/18
// @Time: 12:50

package repository

import (
	"camp/microbook/internal/domain"
	"camp/microbook/internal/repository/cache"
	"camp/microbook/internal/repository/dao/article"
	"context"
	"github.com/ecodeclub/ekit/slice"
	"gorm.io/gorm"
	"time"
)

type ArticleRepository interface {
	Create(ctx context.Context, article domain.Article) (uint64, error)
	Edit(ctx context.Context, article domain.Article) error
	Sync(ctx context.Context, article domain.Article) (uint64, error)
	SyncV1(ctx context.Context, article domain.Article) (uint64, error)
	SyncStatus(ctx context.Context, uid, id uint64, status domain.ArticleStatus) error
	GetByAuthor(ctx context.Context, uid uint64, offset, limit int) ([]domain.Article, error)
	GetById(ctx context.Context, id uint64) (domain.Article, error)
	GetByPubId(ctx context.Context, id uint64) (domain.Article, error)
	ListByOffset(ctx context.Context, start time.Time, offset, size int) ([]domain.Article, error)
}

func NewArticleRepository(d article.ArticleDao, articleCache cache.ArticleCache, uRepo UserRepository) ArticleRepository {
	return &articleRepo{
		dao:   d,
		cache: articleCache,
		user:  uRepo,
	}
}

// NewArticleRepositoryV2 使用两个dao实现存储
// 默认未使用事务
// 使用事务时可在具体的方法中实例化dao
func NewArticleRepositoryV2(aDao article.AuthorDao, rDao article.ReaderDao) ArticleRepository {
	return &articleRepo{
		aDao: aDao,
		rDao: rDao,
	}
}

type articleRepo struct {
	dao   article.ArticleDao
	aDao  article.AuthorDao
	rDao  article.ReaderDao
	user  UserRepository
	db    *gorm.DB
	cache cache.ArticleCache
}

func (repo *articleRepo) ListByOffset(ctx context.Context, start time.Time, offset, size int) ([]domain.Article, error) {
	res, err := repo.dao.ListByOffset(ctx, start, offset, size)
	if err != nil {
		return nil, err
	}
	return slice.Map[article.Articles, domain.Article](res, func(idx int, src article.Articles) domain.Article {
		return repo.toDomain(src)
	}), nil
}

func (repo *articleRepo) GetByPubId(ctx context.Context, id uint64) (domain.Article, error) {
	res, err := repo.cache.Get(ctx, id)
	if err == nil {
		return res, nil
	}
	art, err := repo.dao.GetById(ctx, id)
	if err != nil {
		return domain.Article{}, err
	}
	res = repo.toDomain(art)
	author, err := repo.user.FindById(ctx, art.Creator)
	if err != nil {
		return domain.Article{}, err
	}
	res.Author.Id = art.Creator
	res.Author.Name = author.Nickname

	go func() {
		if err := repo.cache.Set(ctx, res); err != nil {
			//add log
		}
	}()
	return res, nil
}

func (repo *articleRepo) GetById(ctx context.Context, id uint64) (domain.Article, error) {
	res, err := repo.cache.Get(ctx, id)
	if err == nil {
		return res, nil
	}
	art, err := repo.dao.GetById(ctx, id)
	if err != nil {
		return domain.Article{}, err
	}
	res = repo.toDomain(art)
	go func() {
		if err := repo.cache.Set(ctx, res); err != nil {
			//add log
		}
	}()
	return res, nil
}

func (repo *articleRepo) GetByAuthor(ctx context.Context, uid uint64, offset, limit int) ([]domain.Article, error) {
	//判定是否查缓存 只有符合常见的首页查询条件才读取缓存
	if offset == 0 && limit <= 100 {
		res, err := repo.cache.GetFirstPage(ctx, uid)
		if err == nil {
			if len(res) > limit {
				res = res[:limit]
			}
			return res, err
		} else {

		}
	}
	arts, err := repo.dao.GetByAuthor(ctx, uid, offset, limit)
	if err != nil {
		return nil, err
	}
	res := slice.Map(arts, func(idx int, src article.Articles) domain.Article {
		return repo.toDomain(src)
	})

	if offset == 0 && limit == 100 {
		//缓存写入
		//可以考虑用goroutine异步写入
		err = repo.cache.SetFirstPage(ctx, uid, res)
		if err != nil {
			//debug错误日志
		}

	}

	go func() {
		//异步写入  使用新的ctx
		ctx, cancal := context.WithTimeout(context.Background(), time.Second)
		defer cancal()
		repo.preCache(ctx, res)
	}()

	return res, nil
}

func (repo *articleRepo) SyncStatus(ctx context.Context, uid, id uint64, status domain.ArticleStatus) error {
	err := repo.dao.SyncStatus(ctx, uid, id, status.ToUint8())
	if err == nil {
		err = repo.cache.DelFirstPage(ctx, uid)
		if err != nil {
			//debug错误信息
		}
	}
	return err
}

// Sync
func (repo *articleRepo) Sync(ctx context.Context, art domain.Article) (uint64, error) {
	id, err := repo.dao.Sync(ctx, repo.toEntity(art))
	if err == nil {
		err = repo.cache.DelFirstPage(ctx, art.Author.Id)
		if err != nil {
			//debug错误信息
		}
	}
	return id, err
}

// SyncV1 非事务实现
func (repo *articleRepo) SyncV1(ctx context.Context, art domain.Article) (uint64, error) {
	var (
		a   = repo.toEntity(art)
		id  = a.Id
		err error
	)

	if a.Id == 0 {
		id, err = repo.aDao.Create(ctx, a)
	} else {
		err = repo.aDao.Update(ctx, a)
	}

	if err != nil {
		return 0, err
	}
	a.Id = id
	return id, repo.rDao.Upsert(ctx, a)
}

// SyncV2 事务实现(同库不同表,在repo层同步数据)
func (repo *articleRepo) SyncV2(ctx context.Context, art domain.Article) (uint64, error) {
	tx := repo.db.WithContext(ctx).Begin()
	if tx.Error != nil {
		//事务开启失败
		return 0, tx.Error
	}
	//防止后期业务panic  占用连接池
	defer tx.Rollback()

	aDao := article.NewGormAuthorDao(tx)
	rDao := article.NewGormReaderDao(tx)

	var (
		a   = repo.toEntity(art)
		id  = a.Id
		err error
	)

	if a.Id == 0 {
		id, err = aDao.Create(ctx, a)
	} else {
		err = aDao.Update(ctx, a)
	}

	if err != nil {
		return 0, err
	}
	a.Id = id
	err = rDao.UpsertV2(ctx, article.PublishArticle(a))
	if err != nil {
		return 0, err
	}
	tx.Commit()
	return id, nil

}

func (repo *articleRepo) Edit(ctx context.Context, article domain.Article) error {
	err := repo.dao.UpdateById(ctx, repo.toEntity(article))
	if err == nil {
		err = repo.cache.DelFirstPage(ctx, article.Author.Id)
		if err != nil {
			//debug错误信息
		}
	}
	return err
}

func (repo *articleRepo) Create(ctx context.Context, article domain.Article) (uint64, error) {
	id, err := repo.dao.Insert(ctx, repo.toEntity(article))
	if err == nil {
		err = repo.cache.DelFirstPage(ctx, article.Author.Id)
		if err != nil {
			//debug错误信息
		}
	}
	return id, err
}

func (repo *articleRepo) toEntity(art domain.Article) article.Articles {
	return article.Articles{
		Id:      art.Id,
		Title:   art.Title,
		Content: art.Content,
		Creator: art.Author.Id,
		Status:  art.Status.ToUint8(),
	}

}

func (repo *articleRepo) toDomain(art article.Articles) domain.Article {
	res := domain.Article{
		Id:      art.Id,
		Title:   art.Title,
		Content: art.Content,
		Author: domain.Author{
			Id: art.Id,
		},
		Status: domain.ArticleStatus(art.Status),
	}
	res.Abstract = res.Abstraction()
	return res
}

func (repo *articleRepo) preCache(ctx context.Context, res []domain.Article) {
	const sizeThreshHold = 1024 * 1024
	//不缓存大文档
	if len(res) > 0 && len(res[0].Content) <= sizeThreshHold {
		err := repo.cache.Set(ctx, res[0])
		if err != nil {
			//debug 错误
		}

	}
}
